package ee.siimkaspar.subjects.controller;

import java.math.BigDecimal;
import java.security.InvalidParameterException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.opensymphony.xwork2.Validateable;

import ee.siimkaspar.subjects.model.Address;
import ee.siimkaspar.subjects.model.AddressType;
import ee.siimkaspar.subjects.model.Contact;
import ee.siimkaspar.subjects.model.Customer;
import ee.siimkaspar.subjects.model.Employee;
import ee.siimkaspar.subjects.model.Enterprise;
import ee.siimkaspar.subjects.model.ModelInterface;
import ee.siimkaspar.subjects.model.Person;
import ee.siimkaspar.subjects.model.SubjectAttribute;
import ee.siimkaspar.subjects.model.SubjectAttributeType;
import ee.siimkaspar.subjects.model.SubjectType;
import ee.siimkaspar.subjects.service.ContactService;
import ee.siimkaspar.subjects.service.SubjectService;
import ee.siimkaspar.subjects.util.Const;
import ee.siimkaspar.subjects.validator.SubjectValidator;

public class EditSubjectAction extends BaseAction implements Validateable {
	
	public static final int PERSON = 1;
	public static final int ENTERPRISE = 2;
	public static final int EMPLOYEE = 3;
	public static final int CUSTOMER = 4;
	public static final String ACTION_ADD = "addSubjectAction";
	public static final String ACTION_EDIT = "editSubjectAction";
	
	private List<SubjectType> allSubjectTypes;
	private Integer subjectType;
	private SubjectType subjectTypeObject;
	private String action;
	private boolean customerIs = false;
	private Boolean isError;
	private List<String> attributesResultList;
	private final SimpleDateFormat inputDateFormat = new SimpleDateFormat("dd-MM-yyyy");;
	
	private Integer id; //not null if is editing mode
	private Person person;
	private Integer personId;
	private Enterprise enterprise;
	private Integer enterpriseId;
	private Employee employee;
	private Integer employeeId;
	private List<Contact> contacts;
	
	private List<Enterprise> allEnterprises;
	private List<SubjectAttributeType> attributeTypes;
	private Map<String, List<SubjectAttribute>> attributes;
	
	private String firstName;
	private String lastName;
	private Date birthDate;
	private String birthDateString;
	private String ssn;
	private Address address;
	
	private SubjectService sService;
	private ContactService cService;
	
	public EditSubjectAction(SubjectService sService, ContactService cService) {
		this.sService = sService;
		this.cService = cService;
	}
	
	@Override
	public void prepare() throws Exception {
		super.prepare();
		Logger.getLogger(getClass()).debug("PREPARE");
		try {
			if (isError == null || !isError) {
				allSubjectTypes = sService.getAllSubjectTypes();
				allSubjectTypes.remove(2);
				allSubjectTypes.remove(2);
				if (attributes == null) {
					attributes = new HashMap<String, List<SubjectAttribute>>();
				}
				if (subjectType != null) {
					if (personId != null) {
						person = sService.findObject(Person.class, personId);
					}
					if (enterpriseId != null) {
						enterprise = sService.findObject(Enterprise.class, enterpriseId);
					}
					if (employeeId != null) {
						employee = sService.findObject(Employee.class, employeeId);
					}
					subjectTypeObject = sService.findObject(SubjectType.class, subjectType);
					if (subjectTypeObject != null && attributes != null) {
						collectAllAttributes();
					}
				}
				if (birthDateString != null) {
					try {
						this.birthDate = inputDateFormat.parse(birthDateString);
					} catch(Exception e) {
						addFieldError("birthDate", "Invalid date format. Correct is dd-MM-yyyy");
						Logger.getLogger(getClass()).error(e.getMessage());
					}
				}
			}
		} catch (Exception e) {
			Logger.getLogger(getClass()).error("Invalid parameter(s). Error msg=" + e.getMessage());
			e.printStackTrace();
			throw new InvalidParameterException();
		}
	}
	
	public String selectSubjectType() {
		if (allSubjectTypes == null) {
			String errorMsg = "Could not load subject types";
			Logger.getLogger(getClass()).error(errorMsg);
			addActionError(errorMsg);
			return ERROR;
		}
		return SUCCESS;
	}
	
	public String editSubject() {
		String errorMsg;
		try {
			if (subjectType != null) { //is NOT subjectType page
				if (subjectType > 2) {
					Logger.getLogger(getClass()).error("Invalid parameter(s)");
					throw new InvalidParameterException();
				}
				if (id == null) { //subject doesn't exist
					action = ACTION_ADD;
				} else { //subject exists; edit mode
					customerIs = sService.isSubjectCustomer(subjectType, id);
					action = ACTION_EDIT;
					if (loadSubjectData()) {
						collectAllAttributes();
					} else {
						errorMsg = "No such subject!";
						Logger.getLogger(getClass()).error(errorMsg);
						addActionError(errorMsg);
						return ERROR;
					}
				}
				if (address == null) {
					address = new Address();
				}
				return SUCCESS;
			}
		} catch(Exception e) {
			Logger.getLogger(getClass()).error(e.getMessage());
		}
		errorMsg = "No correct subject type selected!";
		Logger.getLogger(getClass()).error(errorMsg);
		addActionError(errorMsg);
		return ERROR;
	}
	
	private void collectAllAttributes() {
		if (id == null) {
			attributeTypes = sService.getAllSubjectAttributeTypes(subjectType);
			List<SubjectAttribute> tmpAttributes = new ArrayList<SubjectAttribute>();
			for (SubjectAttributeType attributeType : attributeTypes) {
				tmpAttributes.add(new SubjectAttribute(attributeType));
			}
			attributes.put(subjectTypeObject.getTypeName(), tmpAttributes);
		} else {
			attributeTypes = sService.getAllSubjectAttributeTypes(subjectType);
			List<SubjectAttribute> tmpAttributes = sService.getAllSubjectAttributes(subjectType, id);
			eventAttributesWithTypes(attributeTypes, tmpAttributes);
			attributes.put(subjectTypeObject.getTypeName(), tmpAttributes);
			if (subjectType == PERSON && person != null && !person.getEmployees().isEmpty()) {
				attributeTypes = sService.getAllSubjectAttributeTypes(EMPLOYEE);
				tmpAttributes= sService.getAllSubjectAttributes(EMPLOYEE, id);
				eventAttributesWithTypes(attributeTypes, tmpAttributes);
				attributes.put(sService.findObject(SubjectType.class, EMPLOYEE).getTypeName(), tmpAttributes);
			}
			if (sService.isSubjectCustomer(subjectType, id)) {
				customerIs = true;
				attributeTypes = sService.getAllSubjectAttributeTypes(CUSTOMER);
				tmpAttributes = sService.getAllSubjectAttributes(CUSTOMER, id);
				eventAttributesWithTypes(attributeTypes, tmpAttributes);
				attributes.put(sService.findObject(SubjectType.class, CUSTOMER).getTypeName(), tmpAttributes);
			}
		}
	}

	private void eventAttributesWithTypes(List<SubjectAttributeType> attributeTypes, List<SubjectAttribute> attributes) {
		if (attributeTypes != null && attributes != null) {
			for (int i = 0; i < attributeTypes.size(); i++) {
				SubjectAttributeType attributeType = attributeTypes.get(i);
				boolean contains = false;
				for (int k = 0; k < attributes.size(); k++) {
					if (attributes.get(k).getSubjectAttributeType().equals(attributeType)) {
						contains = true;
						break;
					}
				}
				if (!contains) {
					attributes.add(new SubjectAttribute(attributeType));
				}
			}
			Collections.sort(attributes);
		}
	}
	
	private boolean loadSubjectData() {
		boolean wasLoaded = false;
		contacts = sService.getSubjectContacts(subjectType, id);
		switch (subjectType) {
		case ENTERPRISE:
			wasLoaded = loadEnterpriseData();
			break;
		case PERSON:
			wasLoaded = loadPersonData();
			break;
		case EMPLOYEE:
			wasLoaded = loadPersonData() && loadEmployeeData();
			break;
		default:
			break;
		}
		return wasLoaded;
	}
	
	private boolean loadEnterpriseData() {
		enterprise = sService.findObject(Enterprise.class, id);
		if (enterprise != null) {
			enterpriseId = enterprise.getId();
			firstName = enterprise.getName();
			lastName = enterprise.getFullName();
			address = sService.getSubjectMainAddress(id, 3);
			return true;
		}
		return false;
	}
	
	public boolean loadPersonData() {
		person = sService.findObject(Person.class, id);
		if (person != null) {
			personId = person.getId();
			firstName = person.getFirstName();
			lastName = person.getLastName();
			birthDate = person.getBirthDate();
			ssn = person.getIdentityCode();
			address = sService.getSubjectMainAddress(id, 1);
			return true;
		}
		return false;
	}
	
	public boolean loadEmployeeData() {
		employee = sService.findObject(Employee.class, id);
		//TODO some employee stuff
		if (employee != null) {
			return true;
		}
		return false;
	}
	
	public String addSubject() {
		try {
			updateAttributeValues(attributesResultList, attributes);
			if (subjectType != null && subjectType < 3) {
				Integer subjectId = null;
				if (subjectType == PERSON) {
					Logger.getLogger(getClass()).info("Saving person");
					Person person = new Person(firstName, lastName, ssn, birthDate);
					sService.save(person);
					subjectId = person.getId();
				} else { //ENTERPRISE
					Logger.getLogger(getClass()).info("Saving enterprise");
					Enterprise enterprise = new Enterprise(firstName, lastName);
					sService.save(enterprise);
					subjectId = enterprise.getId();
				}
				Logger.getLogger(getClass()).info("Subject saved with id=" + subjectId);
				if (address != null) {
					Logger.getLogger(getClass()).info("Saving address");
					address.setAddressType(sService.findObject(AddressType.class, subjectType == PERSON ? 1 : 3));
					address.setSubjectType(subjectTypeObject);
					address.setSubjectFk(subjectId);
					sService.save(address);
				}
				if (attributes != null) {
					Logger.getLogger(getClass()).info("Saving attributes");
					for (String key : attributes.keySet()) {
						for (SubjectAttribute attribute : attributes.get(key)) {
							attribute.setSubjectFk(subjectId);
							sService.save(attribute);
						}
					}
				}
				id = subjectId;
				addActionMessage("New subject saved successfully!");
				return SUCCESS;
			}
		} catch(Exception e) {
			Logger.getLogger(getClass()).error(e.getMessage());
		}
		addActionError("Invalid parameters");
		return ERROR;
	}
	
	public String deleteSubject() {
		try {
			String type = "";
			switch (subjectType) {
			case EditSubjectAction.PERSON:
				Person deletePerson = sService.findObject( Person.class, id);
				sService.remove(deletePerson);
				type = "Person";
				break;
			case ENTERPRISE:
				Enterprise deleteEnterprise = sService.findObject(Enterprise.class, id);
				sService.remove(deleteEnterprise);
				type = "Enterprise";
				break;
			default:
				break;
			}
			deleteAllInList(sService.getSubjectContacts(subjectType, id));
			deleteAllInList(sService.getAllSubjectAddresses(subjectType, id));
			deleteAllInList(sService.getAllSubjectAttributes(subjectType, id));
			addActionMessage(type + " deleted");
			return SUCCESS;
		} catch (Exception e) {
			addActionError("Cannot remove user");
		}
		return ERROR;
	}
	
	private <T extends ModelInterface> void deleteAllInList(List<T> list) {
		for (T mi : list) {
			sService.remove(mi);
		}
	}
	
	public String updateSubject() {
		try {
			if (contacts != null) {
				for (int i = 0; i < contacts.size(); i++) {
					Contact contact = contacts.get(i);
					if (contact == null) { //TODO: remove?
						contacts.remove(i);
						i--;
					} else {
						contact.reAttachMissingData(cService);
						sService.save(contact);
					}
				}
				Logger.getLogger(getClass()).info("Saving contacts");
			}
			updateAttributeValues(attributesResultList, attributes);
			if (subjectType == PERSON) {
				Logger.getLogger(getClass()).info("Saving person");
				person.setFirstName(firstName);
				person.setLastName(lastName);
				person.setIdentityCode(ssn);
				person.setBirthDate(birthDate);
				sService.save(person);
			} else { //ENTERPRISE
				Logger.getLogger(getClass()).info("Saving enterprise");
				enterprise.setName(firstName);
				enterprise.setFullName(lastName);
				if (sService.save(enterprise) == null) {
					addFieldError("", "");
				}
			}
			if (address != null) {
				Logger.getLogger(getClass()).info("Saving address");
				address.setAddressType(sService.findObject(AddressType.class, subjectType == PERSON ? 1 : 3));
				address.setSubjectType(subjectTypeObject);
				address.setSubjectFk(id);
				sService.save(address);
			}
			if (attributes != null) {
				Logger.getLogger(getClass()).info("Saving attributes");
				for (String key : attributes.keySet()) {
					for (SubjectAttribute attribute : attributes.get(key)) {
						attribute.setSubjectFk(id);
						sService.save(attribute);
					}
				}
			}
			if (customerIs && id != null && !sService.isSubjectCustomer(subjectType, id)) {
				Logger.getLogger(getClass()).info("Saving customer");
				Customer customer = new Customer(subjectTypeObject, id);
				sService.save(customer);
			}
			addActionMessage("Subject saved successfully");
			return SUCCESS;
		} catch(Exception e) {
			Logger.getLogger(getClass()).error(e.getMessage());
		}
		addActionError("Error saving subject!");
		return ERROR;
	}
	
	private void updateAttributeValues(List<String> attributeValues, Map<String, List<SubjectAttribute>> attributes) {
		try {
			int i = 0;
			for (String key : attributes.keySet()) {
				for (SubjectAttribute attribute : attributes.get(key)) {
					String value = attributeValues.get(i);
					if (value != null) {
						switch (attribute.getDataType()) {
						case Const.DATA_TYPE_DATE:
							try {
								attribute.setValueDate(inputDateFormat.parse(value)); //TODO: correct
							} catch(Exception e) {
								addFieldError("attributesResultList" + i, "Field value must be a date! Valid format is dd-MM-yyyy");
								Logger.getLogger(getClass()).error(e.getMessage());
							}
							break;
						case Const.DATA_TYPE_NUMBER:
							try {
								attribute.setValueNumber(new BigDecimal(value));
							} catch(Exception e) {
								addFieldError("attributesResultList" + i, "Field value must be a number!");
								Logger.getLogger(getClass()).error(e.getMessage());
							}
							break;
						case Const.DATA_TYPE_STRING:
							attribute.setValueText(value);
						default:
							break;
						}
					}
					i++;
				}
			}
		} catch (Exception e) {
			addFieldError("", "Invalid field");
			e.printStackTrace();
		}
	}

	public List<SubjectType> getAllSubjectTypes() {
		return allSubjectTypes;
	}

	public Integer getSubjectType() {
		return subjectType;
	}

	public void setSubjectType(Integer subjectType) {
		this.subjectType = subjectType;
	}
	
	public void setSubjectType(String subjectType) {
		try {
			this.subjectType = Integer.parseInt(subjectType);
		} catch(Exception e) {
			isError = true;
		}
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}
	
	public void setId(String id) {
		try {
			this.id = Integer.parseInt(id);
		} catch(Exception e) {
			if (!(id == null || id.isEmpty())) {
				isError = true;
			}
		}
	}

	public Person getPerson() {
		return person;
	}

	public void setPerson(Person person) {
		this.person = person;
	}

	public Enterprise getEnterprise() {
		return enterprise;
	}

	public void setEnterprise(Enterprise enterprise) {
		this.enterprise = enterprise;
	}

	public Employee getEmployee() {
		return employee;
	}

	public void setEmployee(Employee employee) {
		this.employee = employee;
	}

	public String getAction() {
		return action;
	}

	public void setAction(String action) {
		this.action = action;
	}

	public Date getBirthDate() {
		return birthDate;
	}

	public void setBirthDate(String birthDate) {
		this.birthDateString = birthDate;
	}

	public String getSsn() {
		return ssn;
	}

	public void setSsn(String ssn) {
		this.ssn = ssn;
	}

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}

	public List<SubjectAttributeType> getAttributeTypes() {
		return attributeTypes;
	}

	public void setAttributeTypes(List<SubjectAttributeType> attributeTypes) {
		this.attributeTypes = attributeTypes;
	}

	public Map<String, List<SubjectAttribute>> getAttributes() {
		return attributes;
	}

	public void setAttributes(Map<String, List<SubjectAttribute>> attributes) {
		this.attributes = attributes;
	}

	public List<Enterprise> getAllEnterprises() {
		return allEnterprises;
	}

	public void setAllEnterprises(List<Enterprise> allEnterprises) {
		this.allEnterprises = allEnterprises;
	}

	public boolean isCustomerIs() {
		return customerIs;
	}

	public void setCustomerIs(boolean customerIs) {
		this.customerIs = customerIs;
	}

	public List<Contact> getContacts() {
		return contacts;
	}

	public void setContacts(List<Contact> contacts) {
		this.contacts = contacts;
	}

	public List<String> getAttributesResultList() {
		return attributesResultList;
	}

	public void setAttributesResultList(List<String> attributesResultList) {
		this.attributesResultList = attributesResultList;
	}

	public Integer getPersonId() {
		return personId;
	}

	public void setPersonId(Integer personId) {
		this.personId = personId;
	}

	public Integer getEnterpriseId() {
		return enterpriseId;
	}

	public void setEnterpriseId(Integer enterpriseId) {
		this.enterpriseId = enterpriseId;
	}

	public Integer getEmployeeId() {
		return employeeId;
	}

	public void setEmployeeId(Integer employeeId) {
		this.employeeId = employeeId;
	}

}
